1
Project Overview
Project Goal
Build a "Flutter Knowledge Quiz" app that presents multiple-choice questions about Flutter and Dart. Users can answer questions, see their progress, view results, and retake the quiz.
Features to Implement
- Multiple-choice questions with 4 options
- Question navigation (next/previous)
- Progress indicator
- Score calculation
- Results screen with detailed feedback
- Timer for each question (optional)
- Quiz restart functionality
2
Project Setup
Step 1: Create Project
flutter create quiz_app
cd quiz_app
Step 2: Add Dependencies
Update pubspec.yaml:
pubspec.yaml
name: quiz_app
description: A Flutter quiz application
version: 1.0.0+1
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.6
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
flutter:
uses-material-design: true
Step 3: Install Dependencies
flutter pub get
3
Project Structure
Suggested Folder Layout
lib/
main.dart
models/
question.dart
quiz_result.dart
data/
quiz_data.dart
screens/
home_screen.dart
quiz_screen.dart
result_screen.dart
widgets/
question_card.dart
option_button.dart
progress_indicator.dart
score_display.dart
4
Creating Question Model
lib/models/question.dart
class Question {
final String id;
final String questionText;
final List options;
final int correctAnswerIndex;
final String? explanation;
Question({
required this.id,
required this.questionText,
required this.options,
required this.correctAnswerIndex,
this.explanation,
});
bool isCorrect(int selectedIndex) {
return selectedIndex == correctAnswerIndex;
}
String get correctAnswer => options[correctAnswerIndex];
}
5
Creating Quiz Result Model
lib/models/quiz_result.dart
class QuizResult {
final int totalQuestions;
final int correctAnswers;
final int wrongAnswers;
final Map userAnswers; // questionId -> selectedOptionIndex
final DateTime completedAt;
QuizResult({
required this.totalQuestions,
required this.correctAnswers,
required this.wrongAnswers,
required this.userAnswers,
required this.completedAt,
});
double get percentage => (correctAnswers / totalQuestions) * 100;
String get grade {
if (percentage >= 90) return 'A+';
if (percentage >= 80) return 'A';
if (percentage >= 70) return 'B';
if (percentage >= 60) return 'C';
if (percentage >= 50) return 'D';
return 'F';
}
String get feedback {
if (percentage >= 90) return 'Excellent! You have a great understanding of Flutter!';
if (percentage >= 80) return 'Great job! You\'re doing well!';
if (percentage >= 70) return 'Good work! Keep practicing!';
if (percentage >= 60) return 'Not bad! Review the topics and try again!';
return 'Keep learning! Review the material and try again!';
}
}
6
Creating Quiz Data
lib/data/quiz_data.dart
import '../models/question.dart';
class QuizData {
static List getFlutterQuiz() {
return [
Question(
id: '1',
questionText: 'What is Flutter?',
options: [
'A mobile app development framework',
'A programming language',
'A database management system',
'A web server',
],
correctAnswerIndex: 0,
explanation: 'Flutter is Google\'s UI toolkit for building natively compiled applications for mobile, web, and desktop from a single codebase.',
),
Question(
id: '2',
questionText: 'Which programming language does Flutter use?',
options: [
'Java',
'Kotlin',
'Dart',
'Swift',
],
correctAnswerIndex: 2,
explanation: 'Flutter uses Dart, a programming language developed by Google.',
),
Question(
id: '3',
questionText: 'What is a Widget in Flutter?',
options: [
'A database table',
'A UI component',
'A network request',
'A file system',
],
correctAnswerIndex: 1,
explanation: 'In Flutter, everything is a widget. Widgets are the basic building blocks of Flutter apps.',
),
Question(
id: '4',
questionText: 'What is the main difference between StatelessWidget and StatefulWidget?',
options: [
'StatelessWidget can change, StatefulWidget cannot',
'StatefulWidget can change, StatelessWidget cannot',
'They are the same',
'StatelessWidget is faster',
],
correctAnswerIndex: 1,
explanation: 'StatefulWidget can change its state during runtime, while StatelessWidget is immutable once created.',
),
Question(
id: '5',
questionText: 'What does "hot reload" do in Flutter?',
options: [
'Restarts the app',
'Updates the UI without losing app state',
'Deletes all data',
'Compiles the app',
],
correctAnswerIndex: 1,
explanation: 'Hot reload allows you to update the UI instantly without losing the current app state.',
),
Question(
id: '6',
questionText: 'What is the purpose of setState()?',
options: [
'To create a new widget',
'To update the state and trigger a rebuild',
'To delete data',
'To navigate to a new screen',
],
correctAnswerIndex: 1,
explanation: 'setState() notifies Flutter that the state has changed and triggers a rebuild of the widget tree.',
),
Question(
id: '7',
questionText: 'What is a Future in Dart?',
options: [
'A completed value',
'A representation of a potential value or error that will be available at some time in the future',
'A list of values',
'A function',
],
correctAnswerIndex: 1,
explanation: 'A Future represents a potential value or error that will be available at some time in the future.',
),
Question(
id: '8',
questionText: 'What is the purpose of async and await?',
options: [
'To make code synchronous',
'To handle asynchronous operations',
'To create loops',
'To define classes',
],
correctAnswerIndex: 1,
explanation: 'async and await are used to handle asynchronous operations in Dart, making asynchronous code look like synchronous code.',
),
Question(
id: '9',
questionText: 'What is a Stream in Dart?',
options: [
'A single value',
'A sequence of asynchronous events',
'A file',
'A database',
],
correctAnswerIndex: 1,
explanation: 'A Stream is a sequence of asynchronous events that can be listened to.',
),
Question(
id: '10',
questionText: 'What is the purpose of Navigator in Flutter?',
options: [
'To manage app state',
'To navigate between screens',
'To store data',
'To make network requests',
],
correctAnswerIndex: 1,
explanation: 'Navigator is used to manage a stack of routes and navigate between different screens in a Flutter app.',
),
];
}
}
7
Creating Option Button Widget
lib/widgets/option_button.dart
import 'package:flutter/material.dart';
class OptionButton extends StatelessWidget {
final String option;
final bool isSelected;
final bool isCorrect;
final bool showAnswer;
final VoidCallback onTap;
const OptionButton({
Key? key,
required this.option,
required this.isSelected,
required this.isCorrect,
required this.showAnswer,
required this.onTap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
Color? backgroundColor;
Color? textColor;
IconData? icon;
if (showAnswer) {
if (isCorrect) {
backgroundColor = Colors.green;
textColor = Colors.white;
icon = Icons.check_circle;
} else if (isSelected && !isCorrect) {
backgroundColor = Colors.red;
textColor = Colors.white;
icon = Icons.cancel;
} else {
backgroundColor = Colors.grey[200];
textColor = Colors.black87;
}
} else {
backgroundColor = isSelected ? Colors.blue : Colors.grey[200];
textColor = isSelected ? Colors.white : Colors.black87;
}
return Padding(
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isSelected ? Colors.blue : Colors.grey[300]!,
width: 2,
),
),
child: Row(
children: [
if (icon != null) ...[
Icon(icon, color: textColor),
SizedBox(width: 12),
],
Expanded(
child: Text(
option,
style: TextStyle(
fontSize: 16,
color: textColor,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
),
),
],
),
),
),
);
}
}
8
Creating Progress Indicator Widget
lib/widgets/progress_indicator.dart
import 'package:flutter/material.dart';
class QuizProgressIndicator extends StatelessWidget {
final int currentQuestion;
final int totalQuestions;
const QuizProgressIndicator({
Key? key,
required this.currentQuestion,
required this.totalQuestions,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final progress = (currentQuestion + 1) / totalQuestions;
return Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Question ${currentQuestion + 1} of $totalQuestions',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Text(
'${(progress * 100).toInt()}%',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
],
),
SizedBox(height: 8),
LinearProgressIndicator(
value: progress,
backgroundColor: Colors.grey[300],
valueColor: AlwaysStoppedAnimation(Colors.blue),
minHeight: 8,
),
],
),
);
}
}
9
Creating Home Screen
lib/screens/home_screen.dart
import 'package:flutter/material.dart';
import 'quiz_screen.dart';
import '../data/quiz_data.dart';
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Quiz'),
centerTitle: true,
),
body: Center(
child: Padding(
padding: EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.quiz,
size: 100,
color: Colors.blue,
),
SizedBox(height: 32),
Text(
'Flutter Knowledge Quiz',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
SizedBox(height: 16),
Text(
'Test your knowledge of Flutter and Dart!',
style: TextStyle(
fontSize: 16,
color: Colors.grey[600],
),
textAlign: TextAlign.center,
),
SizedBox(height: 32),
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
Row(
children: [
Icon(Icons.info_outline),
SizedBox(width: 8),
Text(
'Quiz Information',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
SizedBox(height: 16),
_buildInfoRow('Total Questions', '10'),
_buildInfoRow('Time Limit', 'No limit'),
_buildInfoRow('Passing Score', '60%'),
],
),
),
),
SizedBox(height: 32),
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton.icon(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => QuizScreen(
questions: QuizData.getFlutterQuiz(),
),
),
);
},
icon: Icon(Icons.play_arrow),
label: Text(
'Start Quiz',
style: TextStyle(fontSize: 18),
),
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
],
),
),
),
);
}
Widget _buildInfoRow(String label, String value) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: TextStyle(fontSize: 14),
),
Text(
value,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
],
),
);
}
}
10
Creating Quiz Screen
lib/screens/quiz_screen.dart
import 'package:flutter/material.dart';
import '../models/question.dart';
import '../models/quiz_result.dart';
import '../widgets/option_button.dart';
import '../widgets/progress_indicator.dart';
import 'result_screen.dart';
class QuizScreen extends StatefulWidget {
final List questions;
const QuizScreen({Key? key, required this.questions}) : super(key: key);
@override
_QuizScreenState createState() => _QuizScreenState();
}
class _QuizScreenState extends State {
int _currentQuestionIndex = 0;
Map _userAnswers = {}; // questionId -> selectedOptionIndex
bool _showAnswer = false;
Question get _currentQuestion => widget.questions[_currentQuestionIndex];
bool get _isLastQuestion => _currentQuestionIndex == widget.questions.length - 1;
bool get _isFirstQuestion => _currentQuestionIndex == 0;
int? get _selectedAnswer => _userAnswers[_currentQuestion.id];
void _selectAnswer(int index) {
if (_showAnswer) return;
setState(() {
_userAnswers[_currentQuestion.id] = index;
_showAnswer = true;
});
}
void _nextQuestion() {
if (_isLastQuestion) {
_finishQuiz();
} else {
setState(() {
_currentQuestionIndex++;
_showAnswer = false;
});
}
}
void _previousQuestion() {
if (!_isFirstQuestion) {
setState(() {
_currentQuestionIndex--;
_showAnswer = false;
});
}
}
void _finishQuiz() {
int correctAnswers = 0;
int wrongAnswers = 0;
for (var question in widget.questions) {
final userAnswer = _userAnswers[question.id];
if (userAnswer != null) {
if (question.isCorrect(userAnswer)) {
correctAnswers++;
} else {
wrongAnswers++;
}
} else {
wrongAnswers++;
}
}
final result = QuizResult(
totalQuestions: widget.questions.length,
correctAnswers: correctAnswers,
wrongAnswers: wrongAnswers,
userAnswers: _userAnswers,
completedAt: DateTime.now(),
);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => ResultScreen(
result: result,
questions: widget.questions,
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Quiz'),
actions: [
if (!_isLastQuestion || _showAnswer)
TextButton(
onPressed: _finishQuiz,
child: Text(
'Finish',
style: TextStyle(color: Colors.white),
),
),
],
),
body: Column(
children: [
QuizProgressIndicator(
currentQuestion: _currentQuestionIndex,
totalQuestions: widget.questions.length,
),
Expanded(
child: SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Card(
child: Padding(
padding: EdgeInsets.all(20),
child: Text(
_currentQuestion.questionText,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
),
SizedBox(height: 24),
...List.generate(
_currentQuestion.options.length,
(index) => OptionButton(
option: _currentQuestion.options[index],
isSelected: _selectedAnswer == index,
isCorrect: _currentQuestion.correctAnswerIndex == index,
showAnswer: _showAnswer,
onTap: () => _selectAnswer(index),
),
),
if (_showAnswer && _currentQuestion.explanation != null) ...[
SizedBox(height: 24),
Card(
color: Colors.blue[50],
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.lightbulb_outline, color: Colors.blue),
SizedBox(width: 8),
Text(
'Explanation',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.blue[900],
),
),
],
),
SizedBox(height: 8),
Text(
_currentQuestion.explanation!,
style: TextStyle(color: Colors.blue[900]),
),
],
),
),
),
],
],
),
),
),
Padding(
padding: EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ElevatedButton.icon(
onPressed: _isFirstQuestion ? null : _previousQuestion,
icon: Icon(Icons.arrow_back),
label: Text('Previous'),
),
ElevatedButton.icon(
onPressed: _showAnswer
? _nextQuestion
: (_selectedAnswer != null ? _nextQuestion : null),
icon: Icon(_isLastQuestion ? Icons.check : Icons.arrow_forward),
label: Text(_isLastQuestion ? 'Finish' : 'Next'),
),
],
),
),
],
),
);
}
}
11
Creating Result Screen
lib/screens/result_screen.dart
import 'package:flutter/material.dart';
import '../models/question.dart';
import '../models/quiz_result.dart';
import 'home_screen.dart';
import 'quiz_screen.dart';
import '../data/quiz_data.dart';
class ResultScreen extends StatelessWidget {
final QuizResult result;
final List questions;
const ResultScreen({
Key? key,
required this.result,
required this.questions,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Quiz Results'),
automaticallyImplyLeading: false,
),
body: SingleChildScrollView(
padding: EdgeInsets.all(24),
child: Column(
children: [
// Score Card
Card(
color: _getScoreColor(),
child: Padding(
padding: EdgeInsets.all(24),
child: Column(
children: [
Icon(
_getScoreIcon(),
size: 80,
color: Colors.white,
),
SizedBox(height: 16),
Text(
'${result.percentage.toInt()}%',
style: TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
SizedBox(height: 8),
Text(
'Grade: ${result.grade}',
style: TextStyle(
fontSize: 24,
color: Colors.white,
),
),
SizedBox(height: 16),
Text(
result.feedback,
style: TextStyle(
fontSize: 16,
color: Colors.white,
),
textAlign: TextAlign.center,
),
],
),
),
),
SizedBox(height: 24),
// Statistics
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Statistics',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 16),
_buildStatRow('Total Questions', '${result.totalQuestions}'),
_buildStatRow('Correct Answers', '${result.correctAnswers}', Colors.green),
_buildStatRow('Wrong Answers', '${result.wrongAnswers}', Colors.red),
_buildStatRow('Unanswered', '${result.totalQuestions - result.userAnswers.length}', Colors.orange),
],
),
),
),
SizedBox(height: 24),
// Review Section
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Review Answers',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 16),
...List.generate(
questions.length,
(index) => _buildQuestionReview(questions[index], index),
),
],
),
),
),
SizedBox(height: 24),
// Action Buttons
Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => QuizScreen(
questions: QuizData.getFlutterQuiz(),
),
),
);
},
icon: Icon(Icons.refresh),
label: Text('Retake Quiz'),
),
),
SizedBox(width: 16),
Expanded(
child: ElevatedButton.icon(
onPressed: () {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => HomeScreen()),
(route) => false,
);
},
icon: Icon(Icons.home),
label: Text('Home'),
),
),
],
),
],
),
),
);
}
Color _getScoreColor() {
if (result.percentage >= 80) return Colors.green;
if (result.percentage >= 60) return Colors.blue;
return Colors.orange;
}
IconData _getScoreIcon() {
if (result.percentage >= 80) return Icons.emoji_events;
if (result.percentage >= 60) return Icons.check_circle;
return Icons.sentiment_dissatisfied;
}
Widget _buildStatRow(String label, String value, [Color? valueColor]) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: TextStyle(fontSize: 16),
),
Text(
value,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: valueColor,
),
),
],
),
);
}
Widget _buildQuestionReview(Question question, int index) {
final userAnswer = result.userAnswers[question.id];
final isCorrect = userAnswer != null && question.isCorrect(userAnswer);
return Card(
margin: EdgeInsets.only(bottom: 12),
color: isCorrect ? Colors.green[50] : Colors.red[50],
child: Padding(
padding: EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
isCorrect ? Icons.check_circle : Icons.cancel,
color: isCorrect ? Colors.green : Colors.red,
),
SizedBox(width: 8),
Text(
'Question ${index + 1}',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
],
),
SizedBox(height: 8),
Text(
question.questionText,
style: TextStyle(fontSize: 14),
),
SizedBox(height: 8),
if (userAnswer != null) ...[
Text(
'Your answer: ${question.options[userAnswer]}',
style: TextStyle(
fontSize: 12,
color: isCorrect ? Colors.green[900] : Colors.red[900],
),
),
] else ...[
Text(
'Your answer: Not answered',
style: TextStyle(
fontSize: 12,
color: Colors.orange[900],
),
),
],
Text(
'Correct answer: ${question.correctAnswer}',
style: TextStyle(
fontSize: 12,
color: Colors.green[900],
fontWeight: FontWeight.bold,
),
),
],
),
),
);
}
}
12
Updating Main.dart
lib/main.dart
import 'package:flutter/material.dart';
import 'screens/home_screen.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Quiz',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: HomeScreen(),
);
}
}
13
Project Checklist
Implementation Checklist
- ✅ Project created and dependencies added
- ✅ Folder structure organized
- ✅ Question model created
- ✅ Quiz result model with scoring logic
- ✅ Quiz data with 10 sample questions
- ✅ Option button widget with visual feedback
- ✅ Progress indicator widget
- ✅ Home screen with quiz information
- ✅ Quiz screen with navigation and answer selection
- ✅ Result screen with detailed feedback
- ✅ Main.dart configured
14
Enhancement Ideas
Optional Enhancements
- Add timer for each question or overall quiz
- Implement different quiz categories
- Add difficulty levels (easy, medium, hard)
- Store quiz history locally
- Add leaderboard functionality
- Implement question shuffling
- Add sound effects for correct/wrong answers
- Create a question bank with more questions
- Add image support for questions
- Implement quiz sharing functionality
15
Exercises
1. Complete Implementation
Implement the entire quiz app following all the code provided. Test all features including question navigation, answer selection, and results display.
2. Add Timer
Add a countdown timer for each question (e.g., 30 seconds). If time runs out, automatically move to the next question and mark the current question as unanswered.
3. Add Categories
Create multiple quiz categories (e.g., Flutter Basics, Dart Language, State Management). Allow users to select a category before starting the quiz.